package com.cmpe451.nutty.controller;

import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import com.cmpe451.nutty.model.Comment;
import com.cmpe451.nutty.model.Favorite;
import com.cmpe451.nutty.model.Follow;
import com.cmpe451.nutty.model.Ingredients;
import com.cmpe451.nutty.model.Levels;
import com.cmpe451.nutty.model.Rate;
import com.cmpe451.nutty.model.Recipe;
import com.cmpe451.nutty.model.Report;
import com.cmpe451.nutty.model.Role;
import com.cmpe451.nutty.model.Style;
import com.cmpe451.nutty.model.Unit;
import com.cmpe451.nutty.model.User;
import com.cmpe451.nutty.service.MailService;
import com.cmpe451.nutty.service.RecipeService;
import com.cmpe451.nutty.service.TestService;
import com.cmpe451.nutty.service.UserService;
import com.cmpe451.nutty.so.GuiUser;
import com.cmpe451.nutty.dao.UserDao;
import com.google.gson.Gson;

/**
 * The controller class is the controller of main functions except recipe related ones.
 * @author CmpE Group 2 Fall 2014 nuttyconfirmation@gmail.com
 * @version 2.0, January 2015
 */

@Controller
public class HomeController extends BaseController {

	@Autowired
	private TestService twitterService;

	@Autowired
	private UserService userDetailsService;

	@Autowired
	private RecipeService recipeService;

	@Autowired
	private MailService mailService;

	@Autowired
	private UserDao userDao;

	/*
	 * to be changed for deployment need to create an empty folder on server
	 */
	
	//private String recipePhotoDirectory =
	 //"/Users/alper/Desktop/recipeImages/"; private String
	 //profilePhotoDirectory ="/Users/alper/Desktop/profileImages/";
	 
	/**
	 * @param recipePhotoDirectory the photo directory of recipe on server
	 * @param profilePhotoDirectory the photo directory of profile on server
	 */
	private String recipePhotoDirectory = "/home/project2/nutty/recipeImages/";
	private String profilePhotoDirectory = "/home/project2/nutty/profileImages/";


	/**
	 * @param RECIPE_IMAGE_MAX_WIDTH recipe image maximum width for resize
	 * @param RECIPE_IMAGE_MAX_HEIGHT recipe image maximum height for resize
	 * @param RECIPE_THUMB_MAX_WIDTH recipe thumb nail image maximum width for resize
	 * @param RECIPE_THUMB_MAX_HEIGHT recipe image maximum height for resize
	 * @param RECIPE_LARGE_THUMB_MAX_WIDTH recipe large image maximum width for resize
	 * @param RECIPE_LARGE_THUMB_MAX_HEIGHT recipe image maximum height for resize
	 */
	private int RECIPE_IMAGE_MAX_WIDTH = 500;

	private int RECIPE_IMAGE_MAX_HEIGHT = 500;

	private int RECIPE_THUMB_MAX_WIDTH = 32;

	private int RECIPE_THUMB_MAX_HEIGHT = 32;

	private int RECIPE_LARGE_THUMB_MAX_WIDTH = 140;

	private int RECIPE_LARGE_THUMB_MAX_HEIGHT = 140;

	/**
	 * home function is the default home page of Nutty.
	 * It includes recommended recipes as well as top rated latest recipes.
	 * @param model incoming model from home.jsp
	 * @return home.view tiles definition, bootstrapped view of home view.
	 */
	@RequestMapping(value = "/")
	public Object home(Model model) {
		model.addAttribute("user", getUser());
		if (getUser() != null) {
			String username = getUser().getUsername();
			List<Follow> followList = twitterService.getFollowers(username);
			List<String> follows = new ArrayList<String>();
			if (!followList.isEmpty() && getUser() != null) {

				for (Follow f : followList) {
					if (f.getName().equals(username))
						follows.add(f.getFollows());
				}
				model.addAttribute("follows", follows);
				List<Recipe> latestRecipesInCircle = recipeService
						.getLatestRecipesInCircle(follows);
				model.addAttribute("latestrecipes", latestRecipesInCircle);
			}

			// liked ingredients

			User user = userDetailsService.getUserById(getUser().getId());
			Set<Long> likedRecipes = new HashSet<Long>();
			Set<Long> hatedRecipes = new HashSet<Long>();
			if (user.getLiked_ingredients() != null
					&& user.getHated_ingredients() != null) {
				String[] liked = user.getLiked_ingredients().split(",");
				String[] hated = user.getHated_ingredients().split(",");
				for (String str : liked) {
					for (Recipe recipe : recipeService.findLikedRecipes(str)) {
						likedRecipes.add(recipe.getId());
					}
				}
				for (String str : hated) {
					for (Recipe recipe : recipeService.findDislikedRecipes(str)) {
						hatedRecipes.add(recipe.getId());
					}
				}
				ArrayList<Recipe> returnedRecipes = new ArrayList<Recipe>();
				Set<Long> intersection = new HashSet<Long>(likedRecipes);
				intersection.retainAll(hatedRecipes);
				for (Iterator<Long> it = intersection.iterator(); it.hasNext();) {
					Long f = it.next();
					returnedRecipes.add(recipeService.getRecipeById(f));
				}

				if(hatedRecipes.size() == 0){
					for (Iterator<Long> it = likedRecipes.iterator(); it.hasNext();) {
						Long f = it.next();
						returnedRecipes.add(recipeService.getRecipeById(f));
					}
				}
				model.addAttribute("recommendedRecipes", returnedRecipes);
			}
		}

		List<Recipe> recipes = recipeService.getLatestRecipes(); // ordered by
																	// their
																	// rate
		model.addAttribute("recipes", recipes);
		List<User> list = twitterService.getUsers();

		model.addAttribute("texts", list);
		return "home.view";
	}

	/**
	 * advancedSearch constructs the initial view of advanced search page.
	 * it redirects user if not logged in
	 * @param model incoming model from advancedSearch.jsp
	 * @return tiles definition, bootstrapped view of advancedSearch.jsp
	 */
	@RequestMapping(value = "/advancedSearch")
	public Object advancedSearch(Model model) {
		if(getUser() == null){
			return "login.view";
		}
		model.addAttribute("user", getUser());
		return "advSearch.view";
	}

	/**
	 * @param model incoming model from search.jsp
	 * @param q	query string
	 * @return "searchResults.view tiles definition, bootstrapped view of "searchResults.view.
	 */
	@RequestMapping(value = "/search")
	public Object search(Model model, @RequestParam(required = true) String q) {

		System.out.println("q: " + q);
		List<Recipe> recipes = recipeService.findSearchResults(q);
		List<User> users = userDetailsService.findSearchResultsofUsers(q);

		model.addAttribute("recipes", recipes);
		model.addAttribute("users", users);
		model.addAttribute("user", getUser());

		return "searchResults.view";
	}

	/**
	 * @param model incoming model from advancedSearchResults.jsp
	 * @param liked_ingredients	liked ingredients entered by user
	 * @param hated_ingredients hated ingredients entered by user
	 * @param cookTime specified maximum cook time
	 * @param easiness specified easiness of the recipe
	 * @return
	 */
	@RequestMapping(value = "/advancedSearchResults")
	public Object advancedSearch(Model model,
			@RequestParam(required = false) String liked_ingredients,
			@RequestParam(required = false) String hated_ingredients,
			@RequestParam(required = false) int cookTime,
			@RequestParam(required = false) String easiness) {
		if(getUser() == null){
			return "login.view";
		}
		System.out.println("geldim");
		model.addAttribute("user", getUser());
		Set<Long> likedRecipes = new HashSet<Long>();
		Set<Long> hatedRecipes = new HashSet<Long>();
		String[] liked = liked_ingredients.split(" ");
		for (String str : liked) {
			for (Recipe recipe : recipeService.findLikedRecipes(str, cookTime,
					easiness)) {
				likedRecipes.add(recipe.getId());
			}
		}
		String[] hated = hated_ingredients.split(" ");
		for (String str : hated) {
			for (Recipe recipe : recipeService.findDislikedRecipes(str,
					cookTime, easiness)) {
				hatedRecipes.add(recipe.getId());
			}
		}
		ArrayList<Recipe> returnedRecipes = new ArrayList<Recipe>();
		Set<Long> intersection = new HashSet<Long>(likedRecipes);
		intersection.retainAll(hatedRecipes);
		for (Iterator<Long> it = intersection.iterator(); it.hasNext();) {
			Long f = it.next();
			returnedRecipes.add(recipeService.getRecipeById(f));
		}
		if(hatedRecipes.size() == 0){
			for (Iterator<Long> it = likedRecipes.iterator(); it.hasNext();) {
				Long f = it.next();
				returnedRecipes.add(recipeService.getRecipeById(f));
			}
		}
		model.addAttribute("recipes", returnedRecipes);
		return "advancedSearchResults.view";
	}

	/**
	 * @param model incoming model from semantic search form
	 * @param semanticTags tags entered by user. these tags are used in semantic tagging.
	 * @return advancedSearchResults.view, tiles definition, bootstrapped version of advancedSearchResults.jsp
	 * @throws IOException
	 * @throws JSONException
	 */
	@RequestMapping(value = "/semanticSearch")
	public Object semanticSearch(Model model,
			@RequestParam(required = true) String semanticTags)
			throws IOException, JSONException {
		if(getUser() == null){
			return "login.view";
		}
		model.addAttribute("user", getUser());
		URL url;
		InputStream is = null;
		BufferedReader br;
		String line;
		String[] tags = semanticTags.split(" ");
		Set<String> keywords = new HashSet<String>();
		for (String tag : tags) {
			System.out.println("Tag: " + tag);
			url = new URL("http://conceptnet5.media.mit.edu/data/5.2/c/en/"
					+ tag + "?limit=5");
			is = url.openStream(); // throws an IOException
			br = new BufferedReader(new InputStreamReader(is));

			StringBuilder sb = new StringBuilder();

			while ((line = br.readLine()) != null) {
				sb.append(line);
				// System.out.println(line);
			}
			JSONObject json = new JSONObject(sb.toString());
			JSONArray arr = json.getJSONArray("edges");

			for (int i = 0; i < arr.length(); i++) {
				JSONArray texts = arr.getJSONObject(i).getJSONArray("text");

				for (int j = 0; j < texts.length(); j++) {
					if (!texts.getString(j).equals(tag)
							&& !texts.getString(j).equals("")
							&& !texts.getString(j).contains(" "))
						keywords.add(texts.getString(j));
				}

			}
			keywords.add(tag);

		}
		Set<Recipe> returnedRecipes = new HashSet<Recipe>();
		for (Iterator<String> it = keywords.iterator(); it.hasNext();) {
			String str = it.next();
			List<Recipe> recipes = recipeService
					.findRecipesBasedOnKeywords(str);
			for (Recipe r : recipes) {
				returnedRecipes.add(r);
			}
		}
		model.addAttribute("keywords", keywords);
		List<Recipe> foundRecipes = new ArrayList<Recipe>(returnedRecipes);
		model.addAttribute("recipes", foundRecipes);
		return "advancedSearchResults.view";
	}

	/**
	 * This function fills the public profile of users.
	 * @param model incoming model from publicProfile.jsp
	 * @param username user name of the owner of the public profile, details will be retrieved based on user name
	 * @return publicProfile.view, tiles definition, bootstrapped version of publicProfile.jsp
	 */
	@RequestMapping(value = "/publicProfile")
	public Object publicProfile(Model model,
			@RequestParam(required = false) String username) {

		if (getUser() != null) {
			model.addAttribute("user", getUser());
			if (username.equals(getUser().getUsername()))
				model.addAttribute("same", true);
		}
		System.out.println("Geldim");
		GuiUser user = (GuiUser) userDetailsService
				.loadUserByUsername(username);
		model.addAttribute("userId", user.getId());
		List<Recipe> recipes = recipeService
				.getLatestRecipesByUsername(username);
		model.addAttribute("recipes", recipes);
		User aUser = userDetailsService.getUserById(user.getId());
		Long levelPoint = aUser.getLevel_point();
		Levels level = userDetailsService.getTitle(levelPoint);
		model.addAttribute("title", level.getLevel_name());
		Long maxPoint = level.getMax_val();
		Long minPoint = level.getMin_val();
		if(minPoint == -1){
			minPoint++;
		}
		Long percentage = ((levelPoint-minPoint)*100)/(maxPoint-minPoint);
		model.addAttribute("percentage", percentage);
		model.addAttribute("max", maxPoint);
		model.addAttribute("min", minPoint);
		
		model.addAttribute("currentPoint", levelPoint);
		List<Recipe> myrecipes = recipeService.getRecipes(user);
		model.addAttribute("myrecipes", myrecipes);
		
		List<Follow> followers = twitterService.getFollowings(user.getUsername());
		List<Follow> followings = twitterService.getFollowers(user.getUsername());
		
		List<Favorite> myfavs = recipeService.findFavoritesByUserId(user.getId());
		List<Recipe> myfavrecipes = new ArrayList<Recipe>();
		for(Favorite f : myfavs)
			myfavrecipes.add(recipeService.getRecipeById(f.getRecipeId()));

		model.addAttribute("followers", followers);
		model.addAttribute("followings", followings);
		model.addAttribute("followingCount",
				twitterService.getFollowings(user.getUsername()).size());
		model.addAttribute("followerCount",
				twitterService.getFollowers(user.getUsername()).size());
		

		model.addAttribute("myfavoriterecipes",myfavrecipes);

		String reportPossible;
		if (getUser() != null) {
			if (isReportPossible(username, getUser().getUsername())) {
				reportPossible = "yes";
			} else
				reportPossible = "no";

			model.addAttribute("reportPossible", reportPossible);
		}
		String image_link = aUser.getImage_link();
		model.addAttribute("hasPicture", image_link);
		if (getUser() != null) {
			String currentUsername = getUser().getUsername();
			String state;
			if (isFollowPossible(currentUsername, username))
				state = "possible";
			else
				state = "notpossible";
			System.out.print("state: " + state);
			model.addAttribute("state", state);
		}

		return "publicProfile.view";
	}

	/**
	 * This function calculates the possibility of report. If user already reports user or not.
	 * @param username current user name
	 * @param username2 current visiting user name
	 * @return true if report is possible and vice versa
	 */
	private boolean isReportPossible(String username, String username2) {
		// user'ın report'ladıklarının listesi
		List<Report> reportList = userDetailsService.getReportedOnes(username2);
		for (Report r : reportList) {
			if (r.getReported().equals(username))
				return false;
		}
		return true;
	}

	/**
	 * This function is used when reporting a user.
	 * @param model incoming model from reportUser form
	 * @param username name of the reported user
	 * @return redirects user to public profile page of the reported user
	 */
	@RequestMapping(value = "/reportUser")
	public Object reportUser(Model model,
			@RequestParam(required = false) String username) {

		// Already Reported?
		if(getUser() == null){
			return "login.view";
		}
		
		User user = userDetailsService.getUserById(getUser().getId());
		System.out.println("Geldim");
		Report report = new Report();
		model.addAttribute("user", getUser());
		report.setReported(username);
		report.setReporter(user.getUsername());
		userDetailsService.saveReport(report);
		return "redirect:/publicProfile?username=" + username;
	}

	/**
	 * This function redirects user to the forgot password page.
	 * @param model incoming model from forgotPassword
	 * @return redirects user to the forgotPassword.view, tiles definition, bootstrapped version	
	 */
	@RequestMapping(value = "/forgotPassword")
	public Object forgotPassword(Model model) {

		return "forgotPassword.view";
	}

	/**
	 * This function deactivates user's account and sends a deactivation e-mail
	 * @param model incoming model from deactivate form
	 * @param request Http request for test purposes
	 * @return redirects user to the deactivate.view, tiles definition, bootstrapped version
	 * @throws NoSuchAlgorithmException
	 */
	@RequestMapping(value = "/deactivate")
	public Object deactivate(Model model, HttpServletRequest request)
			throws NoSuchAlgorithmException {
		if(getUser() == null){
			return "login.view";
		}
		User user = userDetailsService.getUserById(getUser().getId());
		user.setValid(0);
		userDetailsService.updateUser(user);
		String currentUrl = makeUrl(request);
		mailService.sendDeactivateMail(user, currentUrl);
		model.addAttribute("deactivated", true);
		return "deactivate.view";
	}

	/**
	 * This function is used for change password purposes.
	 * @param model incoming model from userSettings
	 * @param pass entered new password
	 * @param again entered new password again. checked with pass.
	 * @return redirects user to the profile page
	 */
	@RequestMapping(value = "/userSettings", method = RequestMethod.POST)
	public Object userSettings(Model model,
			@RequestParam(required = true) String pass,
			@RequestParam(required = true) String again) {

		if (pass.equals(again)) {
			User user = userDetailsService.getUserById(getUser().getId());
			user.setPassword(pass);
			userDetailsService.updateUser(user);
		} else
			return "redirect:/profile?mismatch=true";

		model.addAttribute("user", getUser());
		model.addAttribute("change", "ok");
		return "redirect:/profile";
	}

	/**
	 * This function sends an e-mail to user's account which contains current password of the user.
	 * @param model incoming model from forgotPass form
	 * @param username current user name
	 * @return redirects user to the forgotPassword.view, tiles definition, bootstrapped version
	 */
	@RequestMapping(value = "/forgotPass")
	public Object forgotPass(Model model,
			@RequestParam(required = true) String username) {

		User user = userDetailsService.getUserByName(username);
		String pass = user.getPassword();
		String email = user.getEmail();
		// userDetailsService.sendRecoveryMail();
		mailService.sendRecoveryMail(username, email, pass);
		model.addAttribute("sent", email);
		return "forgotPassword.view";
	}

	/**
	 * This function fills the createRecipe.jsp with encessary attributes.
	 * @param model incoming model from createRecipe.jsp form
	 * @param recipeId id of the recipe if the recipe is going to be edited.
	 * @return redirects user to the createRecipe.view, tiles definition, bootstrapped version
	 */
	@RequestMapping(value = "/createRecipe")
	public Object createRecipe(Model model,
			@RequestParam(required = false) Long recipeId) {
		if(getUser() == null){
			return "login.view";
		}
		model.addAttribute("user", getUser());
		List<Ingredients> ingredientList = recipeService.getIngredients();
		List<Unit> unitList = recipeService.getUnits();
		model.addAttribute("units", unitList);
		List<Style> styleList = recipeService.getStyles();
		model.addAttribute("styles", styleList);
		if (recipeId != null) {

			// exception handle etmeyi unutma
			Recipe recipe = recipeService.getRecipeById(recipeId);
			model.addAttribute("recipename", recipe.getRecipe_name());
			model.addAttribute("recipedescription", recipe.getDescription());
			String[] list = amountParser(recipe.getIngredients_list(), 1);
			System.out.println(Arrays.toString(list));
			model.addAttribute("ingredientlist", list);

		}
		// List<Recipe> list = recipeService.
		// model.addAttribute("texts", list);
		model.addAttribute("ingredients", ingredientList);

		return "createRecipe.view";

	}

	/**
	 * This functions saves comments to the database.
	 * @param model incoming model from saveComment form
	 * @param recipeId id of the current recipe
	 * @param comment content of the comment to be saved
	 * @return redirects user to the current recipe page
	 */
	@RequestMapping(value = "/saveComment")
	public Object saveComment(Model model,
			@RequestParam(required = true) Long recipeId,
			@RequestParam(required = true) String comment) {
		if(getUser() == null){
			return "login.view";
		}
		Comment newComment = new Comment();
		newComment.setComment(comment);
		newComment.setRecipe_id(recipeId);
		newComment.setUser(getUser().getUsername());
		newComment.setUserid(getUser().getId());
		recipeService.saveComment(newComment);
		return "redirect:/recipe?recipeId=" + recipeId;

	}

	/**
	 * This function handles with follow requests.
	 * @param model incoming model from saveComment form
	 * @param username user name of the user to be followed
	 * @return redirects user to the current public profile page
	 */
	@RequestMapping(value = "/follow")
	public Object follow(Model model,
			@RequestParam(required = true) String username) {
		if(getUser() == null){
			return "login.view";
		}
		
		GuiUser user = getUser();
		String currentUsername = user.getUsername();
		User follower = userDetailsService.getUserByName(currentUsername);
		User followed = userDetailsService.getUserByName(username);
		Follow follow = new Follow();
		follow.setName(currentUsername);
		follow.setFollows(username);
		
		follow.setName_id(user.getId());
		follow.setFollows_id(followed.getId());
		twitterService.saveFollow(follow);
		User following = userDetailsService.getUserByName(username);
		follower.setLevel_point(follower.getLevel_point() + 10L);
		userDetailsService.updateUser(follower);
		following.setLevel_point(following.getLevel_point() + 15L);
		userDetailsService.updateUser(following);
		return "redirect:/publicProfile?username=" + username;
	}

	/**
	 * This function deals with unfollow requests
	 * @param model incoming model from unfollow form
	 * @param username user name of the user to be followed
	 * @return redirects user to the current public profile page
	 */
	@RequestMapping(value = "/unfollow")
	public Object unfollow(Model model,
			@RequestParam(required = true) String username) {
		if(getUser() == null){
			return "login.view";
		}

		GuiUser user = getUser();
		String currentUsername = user.getUsername();

		Follow follow = twitterService
				.findFollowship(currentUsername, username);
		twitterService.deleteFollow(follow);
		return "redirect:/publicProfile?username=" + username;
	}

	/**
	 * This function rates recipes between 1 and 5
	 * @param model incoming model from rateRecipe form
	 * @param rate the value of rating
	 * @param recipeId the id of recipe going to be rated
	 * @return redirects user to the current recipe page
	 */
	@RequestMapping(value = "/rateRecipe")
	public Object rateRecipe(Model model,
			@RequestParam(required = true) int rate,
			@RequestParam(required = true) Long recipeId) {
		if(getUser() == null){
			return "login.view";
		}

		GuiUser user = getUser();
		User aUser = userDetailsService.getUserById(user.getId());
		String currentUsername = aUser.getUsername();
		Rate newRate = new Rate();
		newRate.setRate(rate);
		newRate.setRecipe_id(recipeId);
		newRate.setUsername(currentUsername);
		recipeService.saveRate(newRate);

		return "redirect:/recipe?recipeId=" + recipeId;
	}


	/**
	 * This function is a parametric function which returns the profile picture of given user.
	 * @param id the id of the user of whom the profile picture is shown
	 * @param response contains picture
	 */
	@RequestMapping(value = "/profile/picture/{id}", method = RequestMethod.GET)
	public void getProfilePicture(@PathVariable String id,
			HttpServletResponse response) {
		try {
			InputStream in = new FileInputStream(new File(profilePhotoDirectory
					+ id + ".png"));

			IOUtils.copy(in, response.getOutputStream());
			response.flushBuffer();
		} catch (Exception e) {
			logger.error("Can't get recipe picture due to {0}", e.getMessage());
		}
	}

	/**
	 * This function displays the basic info on a recipe.
	 * @param model incoming model from recipe.jsp
	 * @param recipeId current id of the recipe
	 * @param request Http request
	 * @return redirects user to the recipe.view, tiles definition, bootstrapped version
	 */
	@RequestMapping(value = "/recipe")
	public Object recipe(Model model,
			@RequestParam(required = true) Long recipeId,
			HttpServletRequest request) {
		String currentUrl = makeUrl(request);
		model.addAttribute("currentUrl", currentUrl);
		model.addAttribute("user", getUser());
		GuiUser user = getUser();
		Recipe recipe = recipeService.getRecipeById(recipeId);
		User aUser = userDetailsService.getUserById(user.getId());
		Rate rate = recipeService.findRatebyRecipe(aUser.getUsername(),
				recipeId);
		if (rate != null)
			model.addAttribute("rateOnRecipe", rate.getRate());
		else
			model.addAttribute("rateOnRecipe", 0);
		List<Comment> recipeComments = recipeService.getCommentsById(recipeId);
		model.addAttribute("comments", recipeComments);
		model.addAttribute("recipeowner", recipe.getRecipe_owner());
		model.addAttribute("recipename", recipe.getRecipe_name());
		model.addAttribute("recipedescription", recipe.getDescription());
		model.addAttribute("recipeId", recipeId);
		String[] list = amountParser(recipe.getIngredients_list(), 1);
		model.addAttribute("ingredientlist", list);

		return "recipe.view";

	}

	/**
	 * This function returns the ingredients saved in database
	 * @param model incoming model from ingredients.jsp
	 * @param ingredientId id of the ingredient
	 * @param request Http request
	 * @return redirects user to the ingredient.view, tiles definition, bootstrapped version
	 */
	@RequestMapping(value = "/ingredient")
	public Object ingredient(Model model,
			@RequestParam(required = true) Long ingredientId,
			HttpServletRequest request) {
		String currentUrl = makeUrl(request);
		Ingredients ingredient = recipeService.getIngredientById(ingredientId);
		model.addAttribute("ingredientName", ingredient.getName());
		model.addAttribute("ingredientDescription",ingredient.getIngredientDescription());
		model.addAttribute("ingredientId", ingredientId);
		model.addAttribute("ingredientName", ingredient.getName());
		model.addAttribute("calory", ingredient.getCalory());
		model.addAttribute("carbs", ingredient.getCarbs());
		model.addAttribute("protein", ingredient.getProtein());
		model.addAttribute("fat", ingredient.getFat());

		return "ingredient.view";

	}

	/**
	 * This function creates a new ingredient.
	 * @param model incoming model from createIngredient.jsp
	 * @param ingredientId the id of the current ingredient
	 * @param request Http request
	 * @return redirects user to the createIngredient.view, tiles definition, bootstrapped version
	 */
	@RequestMapping(value = "/createIngredient")
	public Object createIngredient(Model model,
			@RequestParam(required = false) Long ingredientId,
			HttpServletRequest request) {
		if(getUser() == null){
			return "login.view";
		}
		if (ingredientId != null) {
			String currentUrl = makeUrl(request);
			model.addAttribute("currentUrl", currentUrl);
			model.addAttribute("user", getUser());
			Ingredients ingredient = recipeService
					.getIngredientById(ingredientId);
			model.addAttribute("ingredientName", ingredient.getName());
			model.addAttribute("calory", ingredient.getCalory());
			model.addAttribute("carbs", ingredient.getCarbs());
			model.addAttribute("fat", ingredient.getFat());
			model.addAttribute("protein", ingredient.getProtein());
			model.addAttribute("ingredientDescription",
					ingredient.getIngredientDescription());
			model.addAttribute("ingredientId", ingredientId);
		}

		return "createIngredient.view";

	}

	/**
	 * This function saves ingredient to the database.
	 * @param ingredientDescription the description of the ingredient
	 * @param ingredientName the name of the ingredient
	 * @param protein the protein value of the ingredient
	 * @param fat the fat value of the ingredient
	 * @param calory the calory of the ingredient
	 * @param carbs the carb value of the ingredient
	 * @param ingredientId the id of the ingredient
	 * @return redirects user to the createIngredient page
	 */
	@RequestMapping(value = "/saveIngredient")
	public Object saveIngredient(@RequestParam String ingredientDescription,
			@RequestParam String ingredientName, @RequestParam Double protein,
			@RequestParam Double fat, @RequestParam Double calory,
			@RequestParam Double carbs,
			@RequestParam(required = false) Long ingredientId) {

		Ingredients ingredient = new Ingredients();

		if (ingredientId == null) {
			ingredient = new Ingredients();
			ingredient.setName(ingredientName);
			ingredient.setIngredientDescription(ingredientDescription);
			ingredient.setProtein(protein);
			ingredient.setFat(fat);
			ingredient.setCalory(calory);
			ingredient.setCarbs(carbs);
			recipeService.saveIngredient(ingredient);
		} else {
			ingredient = recipeService.getIngredientById(ingredientId);
			ingredient.setName(ingredientName);
			ingredient.setIngredientDescription(ingredientDescription);
			recipeService.updateIngredient(ingredient);
			// update

			// update

		}
		return "redirect:/createIngredient?success=true";
	}

	
	/**
	 * This function save suser to the database
	 * @param text user name
	 * @param password password
	 * @param email e-mail address
	 * @param file profile picture
	 * @param request hTTP REQUEST
	 * @return redirects user to the home page
	 * @throws NoSuchAlgorithmException
	 */
	@RequestMapping(value = "/saveUser", method = RequestMethod.POST)
	public Object saveUser(@RequestParam String text,
			@RequestParam String password, @RequestParam String email,
			@RequestParam(value = "file", required = false) MultipartFile file,
			HttpServletRequest request) throws NoSuchAlgorithmException {
		User newUser = new User();
		GuiUser user = (GuiUser) userDetailsService.loadUserByUsername(text);

		
		Role role = userDetailsService.getRoleByName("USER");

		String currentUrl = makeUrl(request);
		if(text.equalsIgnoreCase("") || password.equalsIgnoreCase("") || email.equalsIgnoreCase("")){
			return "redirect:/?error_name=invalid";
		}
		User userWithEmail = userDetailsService.getUserByEmail(email);
		
		if(userWithEmail != null){
			return "redirect:/?error_email=alreadyexists";
		}
		if (user == null) {
			newUser.setUsername(text);
			newUser.setPassword(password);
			newUser.setEmail(email);
			newUser.setLevel_point(0L);
			newUser.setValid(0);
			newUser.getRoles().add(role);
			newUser.setName("");
			newUser.setAge(0L);
			twitterService.saveModel(newUser);

			if (file != null && file.getSize() > 0) {
				try {
					String temp = Objects.toString(newUser.getId(), null);
					String fileName = saveProfileFile(file, temp);
					newUser.setImage_link(fileName);
					userDetailsService.updateUser(newUser);
				} catch (IllegalStateException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			// send confirmation mail

			mailService.sendConfirmationMail(newUser, currentUrl);

			return "redirect:/?info=activation";
		} else {
			Map<String, Object> responseBody = new HashMap<String, Object>();
			responseBody.put("error", "User already exists");
			responseBody.put("errorCode", "1");
			return "redirect:/?error_name=alreadyexists";
		}

	}

	/**
	 * This function returns user to the login page
	 * @return redirects to the login.view, bootstrapped version
	 */
	@RequestMapping(value = "/login")
	public Object home2() {
		if (getUser() != null) {
			logger.error("already logged in to the system!");
			return "profile.view";
		}

		return "login.view";

	}

	/**
	 * This function activates the user account by comparing hash values.
	 * @param userid the id of the current user 
	 * @param hash the hash value according to the user name
	 * @return redirect user to the home page with activation
	 * @throws NoSuchAlgorithmException
	 */
	@RequestMapping(value = "/activateUser")
	public Object activateUser(@RequestParam String userid,
			@RequestParam String hash) throws NoSuchAlgorithmException {
		int userId = Integer.parseInt(userid);
		User user = userDetailsService.getUserById(userId);
		if (user.getValid() == 0) {

			String md5 = getMd5(user);
			if (md5.equals(hash)) {
				user.setValid(1);
				userDetailsService.updateUser(user);
				return "redirect:/?active=success";
			} else
				return "redirect:/";

		} else
			return "redirect:/?already=activated";

	}

	/**
	 * This is a dummy method.
	 * @return
	 */
	@RequestMapping(value = "/signup")
	public Object admin() {
		System.out.println("admin executed.");
		return "admin";
	}

	/**
	 * This function creates URL from giving rHttp request
	 * @param request Http request
	 * @return
	 */
	public static String makeUrl(HttpServletRequest request) {
		return request.getRequestURL().toString() + "?"
				+ request.getQueryString();
	}

	/**
	 * This function displays the private profile page
	 * @param model incoming model of the profile.jsp
	 * @param request Http request
	 * @return redirects user to the profile.view, bootstrapped version, in tiles definition
	 */
	@RequestMapping(value = "/profile")
	public Object profile(Model model, HttpServletRequest request) {
		if(getUser() == null){
			return "login.view";
		}

		User userObject = userDetailsService.getUserById(getUser().getId());
		// Role check
		Role role = userDetailsService.getRoleByName("BANNED");
		int reportCount = userDetailsService.checkReportCount(getUser()
				.getUsername());
		System.out.println("Report Count:" + reportCount);
		if (reportCount >= 4) {
			userObject.setValid(2);
			userObject.getRoles().clear();
			userObject.getRoles().add(role);
			userDetailsService.updateUser(userObject);
			model.addAttribute("banned", "banned");
		}

		// Title gelecek
		User thisUser = userDetailsService.getUserById(getUser().getId());
		
		Long levelPoint = userObject.getLevel_point();
		Levels level = userDetailsService.getTitle(levelPoint);
		Long maxPoint = level.getMax_val();
		Long minPoint = level.getMin_val();
		if(minPoint == -1){
			minPoint++;
		}
		Long percentage = ((levelPoint-minPoint)*100)/(maxPoint-minPoint);
		String percent = percentage+"%";
		model.addAttribute("percentage", percent);
		model.addAttribute("max", maxPoint);
		model.addAttribute("min", minPoint);
		model.addAttribute("currentPoint", levelPoint);
		model.addAttribute("title", level.getLevel_name());
		model.addAttribute("likes",thisUser.getLiked_ingredients());
		model.addAttribute("hates", thisUser.getHated_ingredients());
		model.addAttribute("diet", thisUser.getDiet_preferences());
		List<Recipe> list = recipeService.getRecipes(getUser());
		model.addAttribute("recipelist", list);
		model.addAttribute("userId", getUser().getId());
		String user = getUser().getUsername();

		List<Recipe> myrecipes = recipeService.getRecipes(getUser());
		model.addAttribute("myrecipes", myrecipes);
		
		List<Favorite> myfavs = recipeService.findFavoritesByUserId(getUser().getId());
		List<Recipe> myfavrecipes = new ArrayList<Recipe>();
		for(Favorite f : myfavs)
			myfavrecipes.add(recipeService.getRecipeById(f.getRecipeId()));
		
		List<Follow> followers = twitterService.getFollowings(getUser().getUsername());
		List<Follow> followings = twitterService.getFollowers(getUser().getUsername());
		
		model.addAttribute("myfavrecipes", myfavrecipes);

		model.addAttribute("followers", followers);
		model.addAttribute("followings", followings);

		List<Follow> followList = twitterService.getFollowers(user);
		List<String> follows = new ArrayList<String>();
		if (!followList.isEmpty()) {
			for (Follow f : followList) {
				if (f.getName().equals(user))
					follows.add(f.getFollows());
			}
			model.addAttribute("follows", follows);
			List<Recipe> latestRecipesInCircle = recipeService
					.getLatestRecipesInCircle(follows);
			model.addAttribute("latestrecipes", latestRecipesInCircle);
		}

		model.addAttribute("user", userObject);
		model.addAttribute("followingCount",
				twitterService.getFollowings(userObject.getUsername()).size());
		model.addAttribute("followerCount",
				twitterService.getFollowers(userObject.getUsername()).size());
		model.addAttribute("recipeCount", recipeService.getRecipes(getUser())
				.size());
		return "profile.view";
	}

	/**
	 * This function displays the edit profile page.
	 * @param model incoming model from editProfile.jsp
	 * @return redirects user to the editProfile.view, tiles definition, bootstrapped version
	 */
	@RequestMapping(value = "/editProfile")
	public Object editProfile(Model model) {
		if(getUser() == null){
			return "login.view";
		}
		User user = userDetailsService.getUserById(getUser().getId());
		model.addAttribute("user", user);
		model.addAttribute("age", user.getAge());
		model.addAttribute("bio", user.getBio());
		model.addAttribute("hometown", user.getHometown());
		model.addAttribute("diet", user.getDiet_preferences());
		String liked = user.getLiked_ingredients();
		model.addAttribute("liked", liked);
		System.out.println("Liked: " + user.getLiked_ingredients());
		System.out.println("Hated: " + user.getHated_ingredients());
		model.addAttribute("hated", user.getHated_ingredients());
		List<Ingredients> ingredientList = recipeService.getIngredients();
		List<Unit> unitList = recipeService.getUnits();
		model.addAttribute("units", unitList);
		model.addAttribute("ingredients", ingredientList);
		if (user.getCurrent_ingredients() != null) {
			String[] list = amountParser(user.getCurrent_ingredients(), 1);
			model.addAttribute("ingredientlist", list);
		}
		List<Style> styleList = recipeService.getStyles();
		model.addAttribute("styles", styleList);

		return "editProfile.view";
	}

	/**
	 * This function saves the profile settings of the user.
	 * @param model incoming model from saveProfile form.
	 * @param id the id of the user
	 * @param bio the bio info of the user
	 * @param hometown the hometown of the user
	 * @param diet the diet preferences of the user
	 * @param age the age of the user
	 * @param editdata the current ingredients of the user
	 * @param hated the hated ingredients of the user
	 * @param liked liked ingredients of the user
	 * @param file the profile picture of the user
	 * @return redirects user to edit profile page with update on profile
	 */
	@RequestMapping(value = "/saveProfile")
	public Object editProfile(Model model,
			@RequestParam(required = false) Long id,
			@RequestParam(required = false) String bio,
			@RequestParam(required = false) String hometown,
			@RequestParam(required = false) String diet,
			@RequestParam(required = false) Long age,
			@RequestParam(required = false) String editdata,
			@RequestParam(required = false) String hated,
			@RequestParam(required = false) String liked,
			@RequestParam(value = "file", required = false) MultipartFile file) {
		if(getUser() == null){
			return "login.view";
		}
		User user = userDetailsService.getUserById(getUser().getId());
		user.setAge(age);
		user.setBio(bio);
		user.setHometown(hometown);
		user.setDiet_preferences(diet);
		user.setCurrent_ingredients(editdata);
		user.setHated_ingredients(hated);
		user.setLiked_ingredients(liked);

		if (file != null && file.getSize() > 0) {
			try {
				String temp = Objects.toString(user.getId(), null);
				String fileName = saveProfileFile(file, temp);
				user.setImage_link(fileName);
			} catch (IllegalStateException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		userDetailsService.updateUser(user);

		return "redirect:/editProfile?success=true";
	}

	/**
	 * Dummy not found page.
	 * @return custom 404 page
	 */
	@RequestMapping(value = "/notFound")
	public Object notFound() {
		return "notFound";
	}

	/**
	 * Dummy access denied page.
	 * @return custom access denied page.
	 */
	@RequestMapping(value = "/accessDenied")
	public Object accessDenied() {
		return "accessDenied";
	}

	// mode = 1, initial parsing into amounts (e.g 1 teaspoon milk)
	// mode = 2, second parsing into amounts of each (e.g 1, teaspoon, milk)
	/**
	 * This function parses the ingredients
	 * @param amounts the amount of the ingredient
	 * @param mode initial parsing into amounts(=1) or second parsing into amounts of each (=2)
	 * @return
	 */
	protected String[] amountParser(String amounts, int mode) {
		String[] amountList;
		if (mode == 1) {
			amountList = amounts.split(",");
			return amountList;
		} else
			amountList = amounts.split("/");
		return amountList;

	}

	/**
	 * This function calculates if follow is possible
	 * @param user current user name
	 * @param follows the user name which is going to be followed
	 * @return true if it is possible and vice versa
	 */
	protected boolean isFollowPossible(String user, String follows) {

		// user'ın followladıklarının listesi
		List<Follow> followList = twitterService.getFollowers(user);
		for (Follow f : followList) {
			if (f.getFollows().equals(follows))
				return false;
		}
		return true;
	}

	/**
	 * This function saves recipe images
	 * @param multipartFile image of the recipe
	 * @param id id of the recipe
	 * @return id of the image file
	 * @throws IllegalStateException
	 * @throws IOException
	 */
	public String saveRecipeFile(MultipartFile multipartFile, String id)
			throws IllegalStateException, IOException {
		File destinationDir = new File(recipePhotoDirectory);
		if (!destinationDir.exists()) {
			destinationDir.mkdirs();
		}

		String fullPath = recipePhotoDirectory + id;

		BufferedImage originalImage = ImageIO.read(multipartFile
				.getInputStream());
		createThumbnails(originalImage, fullPath);
		resizeImage(originalImage, fullPath);

		return id;
	}

	/**
	 * This function creates thumb nails from original image by resizing it.
	 * @param originalImage original image file
	 * @param fullPath path of the file
	 * @throws IOException
	 */
	protected void createThumbnails(BufferedImage originalImage, String fullPath)
			throws IOException {

		BufferedImage smallThumbnail = createSquareImage(originalImage,
				RECIPE_THUMB_MAX_WIDTH, RECIPE_THUMB_MAX_HEIGHT);
		ImageIO.write(smallThumbnail, "png", new File(fullPath + "_small.png"));
		BufferedImage mediumThumbnail = createSquareImage(originalImage,
				RECIPE_LARGE_THUMB_MAX_WIDTH, RECIPE_LARGE_THUMB_MAX_HEIGHT);
		ImageIO.write(mediumThumbnail, "png",
				new File(fullPath + "_medium.png"));

	}

	/**
	 * This function resizes image.
	 * @param originalImage original image
	 * @param fullPath path of the file
	 * @throws IOException
	 */
	protected void resizeImage(BufferedImage originalImage, String fullPath)
			throws IOException {
		int type = originalImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB
				: originalImage.getType();
		int width = originalImage.getWidth();
		int height = originalImage.getHeight();

		Dimension resizeDimension = getDimensionFitBounds(width, height,
				RECIPE_IMAGE_MAX_WIDTH, RECIPE_IMAGE_MAX_HEIGHT);
		logger.debug(
				"resizeImage::image dimension::[{0}, {1}]::thumbDimension::[{2}, {3}]",
				width, height, resizeDimension.width, resizeDimension.height);

		BufferedImage resizedImage = new BufferedImage(resizeDimension.width,
				resizeDimension.height, type);
		Graphics2D g = resizedImage.createGraphics();
		g.drawImage(originalImage, 0, 0, resizeDimension.width,
				resizeDimension.height, 0, 0, width, height, null);
		g.setComposite(AlphaComposite.Src);
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.setRenderingHint(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		g.dispose();

		ImageIO.write(resizedImage, "png", new File(fullPath + ".png"));
	}

	/**
	 * This function gets the dimension to scale images
	 * @param width width
	 * @param height height
	 * @param boundWidth boundWidth
	 * @param boundHeight boundHeight
	 * @return
	 */
	public static Dimension getDimensionFitBounds(int width, int height,
			int boundWidth, int boundHeight) {
		int original_width = width;
		int original_height = height;
		int new_width = original_width;
		int new_height = original_height;

		// first check if we need to scale width
		if (original_width > boundWidth) {
			// scale width to fit
			new_width = boundWidth;
			// scale height to maintain aspect ratio
			new_height = (new_width * original_height) / original_width;
		}

		// then check if we need to scale even with the new height
		if (new_height > boundHeight) {
			// scale height to fit instead
			new_height = boundHeight;
			// scale width to maintain aspect ratio
			new_width = (new_height * original_width) / original_height;
		}

		return new Dimension(new_width, new_height);
	}

	/**
	 * This function creates a square image from original image
	 * @param originalImage original image
	 * @param maxWidth maximum width of the squared image
	 * @param maxHeight maximum height of the squared image
	 * @return
	 */
	protected BufferedImage createSquareImage(BufferedImage originalImage,
			int maxWidth, int maxHeight) {
		int type = originalImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB
				: originalImage.getType();
		int width = originalImage.getWidth();
		int height = originalImage.getHeight();

		Dimension thumbnailDimension = getDimensionExtendsBounds(width, height,
				maxWidth, maxHeight);

		int dx1 = (maxWidth - thumbnailDimension.width) / 2;
		int dx2 = dx1 + thumbnailDimension.width;
		int dy1 = (maxHeight - thumbnailDimension.height) / 2;
		int dy2 = dy1 + thumbnailDimension.height;

		BufferedImage resizedImage = new BufferedImage(maxWidth, maxHeight,
				type);
		Graphics2D g = resizedImage.createGraphics();
		g.drawImage(originalImage, dx1, dy1, dx2, dy2, 0, 0, width, height,
				null);
		g.setComposite(AlphaComposite.Src);
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.setRenderingHint(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		g.dispose();
		return resizedImage;
	}

	/**
	 * This function creates new dimensions
	 * @param width width
	 * @param height height
	 * @param boundWidth boundWidth
	 * @param boundHeight boundHeight
	 * @return new dimension given that height and weight
	 */
	public static Dimension getDimensionExtendsBounds(int width, int height,
			int boundWidth, int boundHeight) {
		int original_width = width;
		int original_height = height;
		int new_width = original_width;
		int new_height = original_height;

		double widthRate = (double) width / boundWidth;
		double heightRate = (double) height / boundHeight;

		if (widthRate < heightRate) {
			// scale width to fit
			new_width = boundWidth;
			// scale height to maintain aspect ratio
			new_height = (new_width * original_height) / original_width;
		} else {
			// scale height to fit instead
			new_height = boundHeight;
			// scale width to maintain aspect ratio
			new_width = (new_height * original_width) / original_height;
		}

		return new Dimension(new_width, new_height);
	}

	/**
	 * This function saves profile picture into the database
	 * @param multipartFile profile picture
	 * @param id id of the user
	 * @return id of the image
	 * @throws IllegalStateException
	 * @throws IOException
	 */
	@SuppressWarnings("unused")
	protected String saveProfileFile(MultipartFile multipartFile, String id)
			throws IllegalStateException, IOException {
		File destinationDir = new File(profilePhotoDirectory);
		if (!destinationDir.exists()) {
			destinationDir.mkdirs();
		}

		String fullPath = profilePhotoDirectory + id;

		BufferedImage originalImage = ImageIO.read(multipartFile
				.getInputStream());
		createThumbnails(originalImage, fullPath);
		resizeImage(originalImage, fullPath);

		return id;
	}

	/**
	 * Gets md5 hash value of the user name
	 * @param user given user of which user name will be hashed
	 * @return hash value
	 * @throws NoSuchAlgorithmException
	 */
	protected String getMd5(User user) throws NoSuchAlgorithmException {

		String username = user.getUsername();
		MessageDigest md = MessageDigest.getInstance("MD5");
		md.update(username.getBytes());
		byte[] digest = md.digest();
		StringBuffer sb = new StringBuffer();
		for (byte b : digest) {
			sb.append(String.format("%02x", b & 0xff));
		}

		return sb.toString();

	}

}
